class App {
constructor( canvasId ) {
this.cc = document.getElementById( canvasId ).getContext( "2d" );
let scale = 2;
let horizon = 0.9;
let transX = this.cc.canvas.width / scale / 2;
let transY = -this.cc.canvas.height * horizon / scale;
this.cc.scale( scale, -scale );
this.cc.translate( transX, transY );
this.clearRect = function() {
this.cc.clearRect(
-this.cc.canvas.width / scale / 2,
this.cc.canvas.height * horizon / scale,
this.cc.canvas.width / scale,
- this.cc.canvas.height / scale
);
}
/* let slider = document.createElement( "input" );
slider.setAttribute( "type", "range" );
slider.setAttribute( "id", "range1" );
slider.setAttribute( "autocomplete", "on" );
slider.setAttribute( "value", 0 );
slider.setAttribute( "min", 0 );
slider.setAttribute( "step", 1 );
slider.oninput = function( e ) {
this.testpose( e.target.value );
}.bind( this );
slider.style.margin = "auto";
slider.style.display = "block";
this.cc.canvas.after( slider );
*/
let div = document.createElement( "div" );
this.cc.canvas.after( div );
div.outerHTML = ( function() { /*
posing:
test
*/ } ).toString().match( /\/\*([\s\S]*)\*\// )[ 1 ];
document.getElementById( "range1" ).oninput = function( e ) {
//押下時
this.testpose( e.target.value );
let name = Object.keys( this.poses )[ e.target.value ];
document.getElementById( "sliderselected" ).innerHTML = name;
}.bind( this );
this.svcs = new Array();
let world = new SVC_Part( "world" );
world.addJoint( "世界>腹", 0, 80 );
world.addJoint( "世界>キャラ1", 0, 0 );
world.imgd.visibility = true;
world.imgd.type = "horizon";
world.debug1 = true;
let hara = new SVC_Part( "腹" );
hara.imgd.w = 18;
hara.imgd.h = 15;
hara.imgd.x = -12.5;
hara.imgd.y = -15;
hara.addJoint( "腹>胸", 0, 0 );
hara.addJoint( "腹>腰", 0, -12 );
let mune = new SVC_Part( "胸" );
mune.imgd.w = 20;
mune.imgd.h = 20;
mune.imgd.x = -12.5;
mune.imgd.y = -5;
mune.addJoint( "胸>首", 1, 14 );
mune.addJoint( "胸>左上腕", 1, 14 );
let jouwanL = new SVC_Part( "左上腕" );
jouwanL.imgd.w = 10;
jouwanL.imgd.h = 23;
jouwanL.imgd.x = -5;
jouwanL.imgd.y = -23;
jouwanL.addJoint( "左上腕>左前腕", 2, -20 );
let zenwanL = new SVC_Part( "左前腕" );
zenwanL.imgd.w = 10;
zenwanL.imgd.h = 23;
zenwanL.imgd.x = -7;
zenwanL.imgd.y = -21;
zenwanL.addJoint( "左前腕>左手", -2, -20 );
zenwanL.debug1 = true;
let teL = new SVC_Part( "左手" );
teL.imgd.w = 10;
teL.imgd.h = 10;
teL.imgd.x = -5;
teL.imgd.y = -10;
teL.addJoint( "つかみ", 0, -5 );
teL.debug1 = true;
let kubi = new SVC_Part( "首" );
kubi.imgd.w = 10;
kubi.imgd.h = 10;
kubi.imgd.x = -6;
kubi.imgd.y = -2;
kubi.addJoint( "首>頭", 0, 6 );
let atama = new SVC_Part( "頭" );
atama.imgd.w = 20;
atama.imgd.h = 20;
atama.imgd.x = -13;
atama.imgd.y = -1;
let kosi = new SVC_Part( "腰" );
kosi.imgd.w = 20;
kosi.imgd.h = 15;
kosi.imgd.x = -12.5;
kosi.imgd.y = -13;
kosi.addJoint( "腰>左もも", -2.5, -10 );
kosi.addJoint( "腰>右もも", -2.5, -10 );
let momoL = new SVC_Part( "左もも" );
momoL.imgd.w = 18;
momoL.imgd.h = 30;
momoL.imgd.x = -10;
momoL.imgd.y = -28;
momoL.addJoint( "左もも>左すね", -2.5, -27.5 );
let suneL = new SVC_Part( "左すね" );
suneL.imgd.w = 18;
suneL.imgd.h = 30;
suneL.imgd.x = -6.5;
suneL.imgd.y = -28;
suneL.addJoint( "左すね>左かかと", 2.5, -25 );
suneL.debug1 = true;
let kakatoL = new SVC_Part( "左かかと" );
kakatoL.imgd.w = 20;
kakatoL.imgd.h = 10;
kakatoL.imgd.x = -12;
kakatoL.imgd.y = -6;
kakatoL.addJoint( "左かかと>左つま先", -10, -2 );
kakatoL.addJoint( "接地", -10, -5 );
kakatoL.debug1 = true;
let tumasakiL = new SVC_Part( "左つま先" );
tumasakiL.imgd.w = 10;
tumasakiL.imgd.h = 7;
tumasakiL.imgd.x = -10;
tumasakiL.imgd.y = -4;
tumasakiL.addJoint( "接地", -10, 0 );
tumasakiL.debug1 = true;
//build.
world.connect( "世界>腹", hara );
hara.connect( "腹>胸", mune );
mune.connect( "胸>首", kubi );
mune.connect( "胸>左上腕", jouwanL );
jouwanL.connect( "左上腕>左前腕", zenwanL );
zenwanL.connect( "左前腕>左手", teL );
kubi.connect( "首>頭", atama );
hara.connect( "腹>腰", kosi );
kosi.connect( "腰>左もも", momoL );
momoL.connect( "左もも>左すね", suneL );
suneL.connect( "左すね>左かかと", kakatoL );
kakatoL.connect( "左かかと>左つま先", tumasakiL );
let tick = 3.14 / 10;
this.poses = {
chokuritu : function() {
hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" );
this.reset();
},
kagami : function() {
hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" );
this.reset();
atama.rotation = tick * -.5;
kubi.rotation = tick * 0;
mune.rotation = tick * .5;
kosi.rotation = tick * -1;
momoL.rotation = tick * -1.5;
suneL.rotation = tick * 2.5;
kakatoL.rotation = tick * -1.5;
tumasakiL.rotation = tick * 0;
jouwanL.rotation = tick * -4;
zenwanL.rotation = tick * -2;
teL.rotation = tick * -1;
},
senobi : function() {
hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" );
this.reset();
atama.rotation = tick * -1.5;
kubi.rotation = tick * -1;
mune.rotation = tick * -0.5;
kosi.rotation = tick * 0.5;
momoL.rotation = tick * 0.5;
suneL.rotation = tick * 0;
kakatoL.rotation = tick * 2.5;
tumasakiL.rotation = tick * -3;
jouwanL.rotation = tick * -12;
zenwanL.rotation = tick * 0;
teL.rotation = tick * -1;
},
bridge : function() {
hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" );
this.reset();
atama.rotation = tick * -1;
kubi.rotation = tick * -2;
mune.rotation = -3.14 /10;
kosi.rotation = 3.14 /10;
momoL.rotation = 3.14 /6;
suneL.rotation = 3.14 /4;
kakatoL.rotation = 3.14 /4;
tumasakiL.rotation = -3.14 /4;
jouwanL.rotation = tick * -13;
zenwanL.rotation = tick * 0;
teL.rotation = tick * 0;
},
sakadachi : function() {
hara.adjustByThisStop( teL, "つかみ", world );
this.reset();
atama.rotation = tick * -1;
kubi.rotation = tick * -2;
mune.rotation = tick * -0.5;
kosi.rotation = tick * 0.5;
momoL.rotation = tick * 0;
suneL.rotation = tick * 0;
kakatoL.rotation = 3.14 /4;
tumasakiL.rotation = tick * 1;
jouwanL.rotation = tick * -8.85;
zenwanL.rotation = tick * 0;
teL.rotation = tick * -0.5;
},
};
//debug. テスト用スライダの最大値をこのposesの最大値にする
let s = document.getElementById( "range1" );
s.setAttribute( "max", Object.keys( this.poses ).length - 1 );
this.svcs.push( world );
this.testpose( s.value );
}
start() {
if( 1 ) {
this.draw( this.cc );
}
if( 0 ) {
this.timerId = setInterval( function() {
this.draw( this.cc );
this.flg = ! this.flg;
}.bind( this ), 500 );
}
}
stop() {
clearInterval( this.timerId );
}
draw( cc ) {
this.clearRect();
for( let i = 0; i < this.svcs.length; i++ ) {
let svc = this.svcs[ i ];
svc.draw( cc );
}
}
reset() {
for( let i = 0; i < this.svcs.length; i++ ) {
let svc = this.svcs[ i ];
svc.reset();
}
}
calc() {
for( let i = 0; i < this.svcs.length; i++ ) {
let svc = this.svcs[ i ];
svc.calc();
}
}
testpose( value ) {
let name = Object.keys( this.poses )[ value ];
this.poses[ name ].call( this );
this.calc();
console.log( name );
this.draw( this.cc );
}
}//App
class SVC_Part {
constructor( title ) {
this.title = title;
this.jointHash = new Object();
this.rotation = 0;
this.adjustFrom = null;
this.adjustFromJointName = null;
this.adjustTo = null;
this.adjustToJointName = null;
this.abs = new Object();
this.parent = null;
this.parentJoint = null;
this.children = new Array();
this.imgd = new TestRect( "rect" );
this.debug1 = false;
}
addJoint( name, x, y ) {
let joint = new Object();
joint.name = name;
joint.x = x;
joint.y = y;
this.jointHash[ name ] = joint;
}
//---connect
//他のSVC_Partを自身のJointへ接続
connect( jointName, part ) {
this.children.push( part );
part.parent = this;
//check.
if( ! this.jointHash[ jointName ] ) {
alert( "undefined key '" + jointName + "' in jointHash" );
}
part.parentJoint = this.jointHash[ jointName ];
}
//たとえば、足を地面に接地する等
adjust( adjustFrom, adjustFromJointName, adjustTo, adjustToJointName ) {
this.adjustFrom = adjustFrom;
this.adjustFromJointName = adjustFromJointName;
this.adjustTo = adjustTo;
this.adjustToJointName = adjustToJointName;
}
adjustByThisStop( adjustFrom, adjustFromJointName, adjustTo ) {
//check. calc()が行われる前はabs.jointHashは未作成
if( ! adjustFrom.abs.jointHash ) {
alert( ( function() {/*
最初の描画でadjustByThisStop()を行おうとしましたが、
adjustByThisStop()は「前回の描画をもとにして
adjust()を行うメソッド」なので実行できません。
*/} ).toString().match( /\/\*\s*([\s\S]*)\s*\*\// )[ 1 ].replace( /\t/g, "" ) );
return;
}
let joint = adjustFrom.abs.jointHash[ adjustFromJointName ];
adjustTo.addJoint( "this stop", joint.x, joint.y );
this.adjust( adjustFrom, adjustFromJointName, adjustTo, "this stop" );
}
reset() {
this.rotation = 0;
//子
for( let i = 0; i < this.children.length; i++ ) {
let child = this.children[ i ];
child.reset();
}
}
calc() {
//自身の値の絶対座標を求める。
if( ! this.parent ) {
//親がない場合は
this.abs.x = 0;
this.abs.y = 0;
this.abs.rotation = this.rotation;
} else {
//親がある場合は
//check.
if( ! this.parentJoint ) {
alert( "undefined parentJoint at " + this.title );
}
let parentJointAbs = this.parent.abs.jointHash[ this.parentJoint.name ];
this.abs.x = parentJointAbs.x;
this.abs.y = parentJointAbs.y;
this.abs.rotation = this.parent.abs.rotation + this.rotation;
}
//各jointの絶対座標を求める。
this.abs.jointHash = new Object();
for( let name in this.jointHash ) {
let j = this.jointHash[ name ];
let res = this.mathRotate( j.x, j.y, this.abs.rotation );
this.abs.jointHash[ name ] = {
x : this.abs.x + res.X,
y : this.abs.y + res.Y,
}
}
//子もcalc()
for( let i = 0; i < this.children.length; i++ ) {
let child = this.children[ i ];
child.calc();
}
//---接地
if( this.adjustFrom ) {
let from = this.adjustFrom.abs.jointHash[ this.adjustFromJointName ];
let to = this.adjustTo.abs.jointHash[ this.adjustToJointName ];
let adjustX = to.x - from.x;
let adjustY = to.y - from.y;
let adjustRotation = this.adjustTo.abs.rotation - this.adjustFrom.abs.rotation;
let cx = to.x;
let cy = to.y;
this.calc2( adjustX, adjustY, adjustRotation, cx, cy );
}
}//calc()
calc2( adjustX, adjustY, adjustRotation, cx, cy ) {
this.abs.x += adjustX;
this.abs.y += adjustY;
this.abs.rotation += adjustRotation;
//adjust回転
let res = this.mathRotateC( this.abs.x, this.abs.y, cx, cy, adjustRotation );
this.abs.x = res.X;
this.abs.y = res.Y;
//各jointについてadjust回転
for( let name in this.abs.jointHash ) {
let x = this.abs.jointHash[ name ].x + adjustX;
let y = this.abs.jointHash[ name ].y + adjustY;
let res = this.mathRotateC( x, y, cx, cy, adjustRotation );
this.abs.jointHash[ name ].x = res.X;
this.abs.jointHash[ name ].y = res.Y;
}
//子について
for( let i = 0; i < this.children.length; i++ ) {
let child = this.children[ i ];
child.calc2( adjustX, adjustY, adjustRotation, cx, cy );
}
}
//SVC_Part
draw( cc ) {
//自身を描画
cc.save();
cc.translate( this.abs.x, this.abs.y );
cc.rotate( this.abs.rotation );
cc.translate( this.imgd.x, this.imgd.y );
this.imgd.draw( cc );
cc.restore();
//子を描画
for( let i = 0; i < this.children.length; i++ ) {
let child = this.children[ i ];
child.draw( cc );
}
//debug.
if( this.debug1 ) {
//各jointを○で示す
for( let name in this.jointHash ) {
let joint = this.abs.jointHash[ name ];
cc.save();
cc.translate( joint.x, joint.y );
cc.scale( 1, -1 );
cc.globalAlpha = .3;
cc.beginPath();
cc.arc( 0, 0, 3, 0, 6.28, false );
cc.closePath();
cc.strokeStyle = "red";
cc.stroke();
cc.font = "6px''";
cc.fillText( name, 0, 0 );
cc.restore();
}
}
}//draw()
//---
mathRotate( x, y, theta2 ) {
let theta1 = Math.atan2( y, x );
let hankei = Math.sqrt( x * x + y * y );
return {
X : Math.cos( theta1 + theta2 ) * hankei,
Y : Math.sin( theta1 + theta2 ) * hankei,
}
}
mathRotateC( x, y, cx, cy, theta2 ) {
x -= cx;
y -= cy;
let theta1 = Math.atan2( y, x );
let hankei = Math.sqrt( x * x + y * y );
return {
X : Math.cos( theta1 + theta2 ) * hankei + cx,
Y : Math.sin( theta1 + theta2 ) * hankei + cy,
}
}
}//SVC_Part
class TestRect {
constructor( type ) {
this.x = 0;
this.y = 0;
this.w = 10;
this.h = 10;
this.visibility = true;
this.type = type;
}
draw( cc ) {
//check.
if( ! this.visibility ) return;
switch( this.type ) {
case "rect":
cc.strokeRect( 0, 0, this.w, this.h );
break;
case "horizon":
cc.fillStyle = "brown";
let w = 130;
let h = 23;
cc.fillRect( -w, -h, w * 2, h );
break;
default:
alert( "undefined type: '" + this.type + "'" );
}
}
}